#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Globalization;

namespace Yarr
{
	/// <summary>
	/// Special forms take their arguents un-evaluated so that
	/// they can themselves decide upon the evaluation process.
	/// </summary>
    [Package("system")]
	internal static class SpecialForms
	{
		/// <summary>
        /// (and expr1 expr2 ... exprN)
		/// Returns true if all arguments are true, false otherwise.
		/// Performs short circuit evaluation on its arguments.
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
        [SpecialForm("and")]
        public static Object And(Cons args, Environment environment) 
		{
            if (args == null)
                return true;

            Object result = true;
            foreach (Object item in args)
            {
                if (!Conversions.ObjectToBoolean(result = Runtime.Eval(item, environment)) ||
                        Runtime.Returning)
                    break;
            }

			return result;
		}


        /// <summary>
        /// `(form)
        /// Expand a macro definition
        /// </summary>
        /// <param name="args">The macro to expand</param>
        /// <param name="environment">The local environment</param>
        /// <returns>The expanded macro</returns>
        [SpecialForm("backquote")]
		public static Object BackQuote(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            return Runtime.BackQuoteExpand(args.First(), environment);
		}


        /// <summary>
        /// (bind-setf-method name closure)
        /// Defines a new SETF method
        /// </summary>
        /// <param name="args">A list with two elements; the first is the name of the method, 
        /// the second is the function to bind</param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("bind-setf-method")]
        public static Object BindSetf (Cons args, Environment environment)
        {
            if (environment == null)
                throw new YarrException("Bind-setf-method: Environment is null");

            if (args == null  ||  args.Count != 2)
                throw new YarrException("Bind-setf-method: Expecting exactly two arguments");

            object name = args.First();
            
            if (!(name is Symbol) && !(name is String))
                throw new YarrException("Bind-setf-method: First argument must be a symbol");
            
            Symbol method = (name is Symbol) ? (Symbol)name : Symbol.FromName((String)name);

            object closure = Runtime.Eval(args.Second(), environment);
            if (closure is Closure)
                environment.BindSetf(method, (Closure)closure);
            else
                throw new YarrException("Bind-setf-method: Second argument must be a function");

            return closure;
        }


        /// <summary>
		/// (call method object arg1 arg2 ... argN)
		/// Calls a .NET method on a given object with given arguments. 
		/// This is useful if the method name clashes with a variable which is already 
		/// bound in the current Yarr lexical environment. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("call")]
        public static Object Call(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            return Runtime.Call(args.Car().ToString(), Runtime.EvalRaw(args.Second(), environment),
				                (Cons)args.Cddr(), environment);
		}

		/// <summary>
		/// (cond [test expression]* [default])
		/// Evaluates tests until one returns true. If a test returns true, 
		/// then evaluates its corresponding expression. If no test return true, 
		/// then and optionally specified default expression is evaluated. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("cond;if")]
        public static Object Cond(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            Cons clauses = args;

			while (clauses != null)
			{
				if (clauses.Count == 1) 
				{
					// This is a default (else) clause, so just execute it
					return Runtime.Eval(clauses.First(),environment);
				}

				if (clauses.Count >= 2) 
				{
					if (Conversions.ObjectToBoolean(Runtime.Eval(clauses.First(),environment)))
						return Runtime.Eval(clauses.Second(),environment);
					else
						clauses = (Cons)clauses.Cddr();
				}
			}	
			return null;
		}


        /// <summary>
        /// (declare (symbol type [value])* )
        /// Declare symbols to be of a particular type
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("declare")]
        public static Object Declare(Cons args, Environment environment)
        {
            Object value = null;

            while (args != null)
            {
                Cons binding = (Cons)args.First();

                if (!environment.IsDeclared((Symbol)binding.First()))
                    value = environment.Declare((Symbol)binding.First(),
                                                (Type)Runtime.Eval(binding.Second(), environment), 
                                                Runtime.Eval(binding.Third(), environment));
                else if (binding.Third() != null)
                    value = environment.AssignLocal((Symbol)binding.First(),
                                            Runtime.Eval(binding.Third(), environment));

                args = (Cons)args.Rest();
            }

            return value;
        }


        /// <summary>
        /// (defvar symbol type argument*)
        /// Decalres a parameter to a function to be of a particular type
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns>The defined symbol</returns>
        [SpecialForm("defvar")]
        public static Object DefVar (Cons args, Environment environment)
        {
            object sym = (Symbol)args.First();
            Type type = (Type)Runtime.Eval(((Cons)args.Rest()).Car(), environment);
            object result = null;

            // Are we being passed constructor parameters?

            if (args.Cddr() == null)
            {
                if (Type.GetTypeCode(type) == TypeCode.Object)
                    result = null;
                else
                    result = Runtime.MakeInstance(type, null);
            }
            else
                result = Runtime.Eval(new Cons(Symbol.NEW, args.Rest()), environment);

            // Bind the symbol and return it
            environment.Declare((Symbol)sym, type, result);

            return sym;
        }


		/// <summary>
		/// (do expr1 expr2 ... exprN)
		/// Evaluates each expression, one at a time. 
		/// Returns the result of the evaluation of the last expression. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
        [SpecialForm("do")]
		public static Object Do(Cons args, Environment environment) 
		{
            return Runtime.Do(args, environment);
		}

        [SpecialForm("return")]
        private static Object Return (Cons args, Environment environment)
        {
            Object retVal;

            if (args.Length > 1)
                throw new YarrException("Return: expecting at most one argument");
            else
                retVal = Runtime.Eval(args.Car(), environment);

            Runtime.Returning = true;

            return retVal;
        }

		/// <summary>
		/// (fn arguments body)
		/// Defines a closure with the specified list of arguments and the specified body, 
		/// a Yarr expression. Not unlike a lambda expression in Common Lisp.NB arguments 
		/// is a simple list, we dont yet support keyword or optional arguments. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("fn;lambda")]
        public static Object Fn(Cons args, Environment environment) 
		{
            if (args == null)
                throw new YarrException("Fn: Expecting lambda declaration");

            Type returnType = typeof(Object);

            Cons argumentList = (Cons)args.Car();
            Cons fnBody = (Cons)args.Cdr();

            if (fnBody.Car() is Symbol && fnBody.Car() == Symbol.Keyword("returns"))
            {
                returnType = (Type)Runtime.Eval(fnBody.Cadr(), environment);
                fnBody = (Cons)fnBody.Cddr();
            }

            return new Closure(argumentList, fnBody, returnType, environment);
		}


        /// <summary>
        /// (for init-form test-form iter-form expr1 expr2 ... exprN) 
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("for")]
        public static Object For (Cons args, Environment environment)
        {
            if (args == null  ||  args.Length < 3)
                throw new YarrException("For: expecting at least three argumetns");

            Object test = args.Cadr();
            Object iter = args.Caddr();
            Object body = args.Cdddr();

            Object retVal = null;

            Runtime.Eval(args.Car(), environment);
            while (Conversions.ObjectToBoolean(Runtime.Eval(test, environment)))
            {
                retVal = Runtime.Do((Cons)body, environment);
                if (Runtime.Returning)
                    break;

                Runtime.Eval(iter, environment);
            }

            return retVal;            
        }


		/// <summary>
		/// (each symbol IEnumerable expression)
		/// Iterates over any object which impelements IEnumerablewith succesive 
		/// elements being assigned to a variable named symbol; exceutes expression 
		/// on each iteration. Cons (Yarr lists), as well as many .NET collections 
		/// are IEnumerable. Foreach is a synonym for each. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
        [SpecialForm("foreach;each")]
		public static Object ForEach(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            Environment localEnvironment = Environment.MakeEnvironment(environment);

			Symbol variable = (Symbol) args.First();
			Object list = Runtime.Eval(args.Second(),localEnvironment);

            if (!(list is IEnumerable))
                throw new YarrException("each: Expecting IEnumerable argument");

            object result = null;
			foreach (object o in (IEnumerable)list) 
			{
                localEnvironment.AssignLocal(variable, o);
                result = Runtime.Do((Cons)args.Cddr(), localEnvironment);
                if (Runtime.Returning)
                    break;
			}

			return result;
		}

        /// <summary>
        /// (function (method-name instance))
        /// Obtain a delegate for a .NET method
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("function")]
        public static Object Function(Cons args, Environment environment)
        {
            if (args == null || args.Length != 1)
                throw new YarrException("Function: Expecting a single function or function reference");

            Object fnRef = args.First();
            if (fnRef is Symbol)
            {
                Symbol fnName = (Symbol)fnRef;
                if (!fnName.Name.Contains("."))
                {
                    Object fn = environment.GetValue(fnName);
                    if ((fn is Closure) || (fn is Delegate))
                        return fn;
                    else
                        throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                        "Function: {0} is not a function", args.First()));
                }
                else
                    fnRef = Primitives.ParseDotNetReference(fnName, null, false, environment);
            }

            if (!(fnRef is Cons) ||  ((Cons)fnRef).Length != 2)
                throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                                        "Function: {0} is not a function reference", args.First()));

            Cons methodRef = (Cons)fnRef;

            string methodName = methodRef.First().ToString();
            if (String.IsNullOrEmpty(methodName))
                throw new YarrException("Function: No method name specified");

            Object instance = Runtime.Eval(methodRef.Second(), environment);
            if (instance == null)
                throw new YarrException("Function: No instance or class specified");

            Cons paramList = new Cons(Symbol.REST,
                             new Cons(Symbol.FromName("args")));

            Cons closureBody = new Cons(new Cons(Symbol.EVAL, new Cons(
                                            new Cons(Symbol.BACKQUOTE, new Cons(
                                                new Cons(Symbol.CALL,
                                                    new Cons(methodName,
                                                    new Cons(instance, (new Cons(
                                                        new Cons(Symbol.SPLICE,
                                                        new Cons(Symbol.FromName("args")))))))))))));

            return new Closure(paramList, closureBody, environment);
        }

        /// <summary>
		/// (let symbol value expression*)
        /// (let* symbol value expression*)
        /// (let ([symbol | (symbol value) | ((symbol type) value)]* ) expression*)
        /// (let* ([symbol | (symbol value) | ((symbol type) value)]* ) expression*)
        /// (letv (symbol-list) (value-list) expression*)
		/// Binds a new local variable symbol to value in a new local lexical environment, 
		/// before evaluating expressions. Let an Let* Can bind either a single symbol, or a list of
        /// local variables. The difference between let and let* is that values for the bound
        /// locals are evaluated either in the caller's environment (let) or the new
        /// local environment (let*). Letv binds each symbol in symbol-list to the corresponding
        /// value in value-list.
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
        [SpecialForm("let")]
        public static Object Let (Cons args, Environment environment)
        {
            return LetHelper(args, environment, false, "let");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("let*;with")]
        public static Object LetStar (Cons args, Environment environment)
        {
            return LetHelper(args, environment, true, "let*");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("letv")]
        public static Object Letv (Cons args, Environment environment)
        {
            Environment localEnvironment = Environment.MakeEnvironment(environment);

            Cons symbolList = (Cons)Runtime.Eval(args.Car(), environment);
            Cons valueList = (Cons)Runtime.Eval(args.Cadr(), environment);

            while (symbolList != null)
            {
                if (symbolList.Car() is Symbol)
                {
                    if (valueList != null)
                    {
                        localEnvironment.AssignLocal((Symbol)symbolList.Car(), valueList.Car());
                        valueList = (Cons)valueList.Cdr();
                    }
                    else
                        localEnvironment.AssignLocal((Symbol)symbolList.Car(), null);
                }
                else
                    throw new YarrException(
                            String.Format(CultureInfo.CurrentCulture, 
                                            "Letv: {0} is not a symbol", symbolList.Car()));

                symbolList = (Cons)symbolList.Cdr();
            }

            return Runtime.Do((Cons)args.Cddr(), localEnvironment);
        }

        private static Object LetHelper(Cons args, Environment environment, bool localEval, string callerName) 
		{
            if (args == null)
                return null;

            Environment localEnvironment = Environment.MakeEnvironment(environment);

            LetBinder(args, localEnvironment, localEval ? localEnvironment : environment, callerName);

            Cons forms = (args.First() is Symbol) ? (Cons)args.Cddr() :
                         (args.First() is Cons)   ? (Cons)args.Cdr()  :
                         null;

			// Now process the remaining forms as an implied DO:
            if (forms == null)
                return null;

            return Runtime.Do(forms, localEnvironment);
		}

        private static void LetBinder (Cons args, Environment bindingEnv, Environment evalEnv, string callerName)
        {
            // Single local variable & assignment
            if (args.First() is Symbol)
                bindingEnv.AssignLocal((Symbol)args.First(), Runtime.Eval(args.Second(), evalEnv));

            // list of local variables & (optional) assignments
            else if (args.First() is Cons)
            {
                foreach (object binding in (Cons)args.First())
                {
                    if (binding is Symbol)
                        bindingEnv.AssignLocal((Symbol)binding, null);

                    else if (binding is Cons)
                    {
                        if (((Cons)binding).First() is Cons)
                        {
                            if (((Cons)binding).Caar() is Symbol)
                            {
                                bindingEnv.Declare((Symbol)((Cons)binding).Caar(),
                                                    (Type)Runtime.Eval(((Cons)binding).Cadar(), evalEnv),
                                                    Runtime.Eval(((Cons)binding).Second(), evalEnv));
                            }
                            else
                                throw new YarrException(
                                        String.Format(CultureInfo.CurrentCulture, 
                                                "{1}: {0} is not a symbol", 
                                                ((Cons)binding).Caar(), callerName));
                        }
                        else if (((Cons)binding).First() is Symbol)
                            bindingEnv.AssignLocal((Symbol)((Cons)binding).First(),
                                                            Runtime.Eval(((Cons)binding).Second(), evalEnv));
                        else
                            throw new YarrException(
                                    String.Format(CultureInfo.CurrentCulture, 
                                            "{1}: {0} is not a symbol", 
                                            ((Cons)binding).First(), callerName));
                    }
                    else
                        throw new YarrException(
                                String.Format(CultureInfo.CurrentCulture, 
                                        "{1}: {0} is neither a symbol nor a binding", 
                                        binding.ToString(), callerName));
                }
            }
            else
                throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                            "{0}: Expecting binding declarations", callerName));
        }


        /// <summary>
        /// (load filename)
        /// Loads and evaluates all statements in the given 
        /// filename, which must be a text file. 
        /// </summary>
        /// <param name="args">A list containing the filename to load</param>
        /// <param name="environment">The local environnment</param>
        /// <returns>The full path to the file that was processed, or the assembly name of the DLL that was loaded</returns>
        [SpecialForm("load")]
        public static Object Load(Cons args, Environment environment)
        {
            if (args.Count != 1)
                throw new YarrException("Load: Expecting exactly one argument");

            Object loadPath = Runtime.Load((string)Runtime.Eval(args.Car(), environment), null, environment);

            return loadPath;
        }


        /// <summary>
		/// (macro arguments expression*)
		/// Defines a macro. Similar in some respects to defining a clsoure using fn 
		/// except that the expressions are expanded using macroexpand in the current 
		/// environment before being evaluated. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("macro")]
        public static Object Macro(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            return new Macro((Cons)args.First(), (Cons)args.Cdr(), environment);
		}


		/// <summary>
		/// (or expression*)
		/// Evaluates the expressions in order, returning true if any expression is true, 
		/// and false if all expressions are false. If a true expression is encountered, 
		/// then the remaining expressions are not evaluated (short circuit evaluation). 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("or")]
        public static Object Or(Cons args, Environment environment) 
		{
            if (args == null)
                return false;

            Object result = false;
            foreach (Object item in args)
            {
                if (Conversions.ObjectToBoolean(result = Runtime.Eval(item, environment)) ||
                        Runtime.Returning)
                    break;
            }

            return result;
		}


		/// <summary>
		/// (quote object)
		/// Returns object without evaluating it. object may be any .NET object. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "environment")]
        [SpecialForm("quote")]
        public static Object Quote(Cons args, Environment environment) 
		{
            return (args == null) ? null : args.First();
		}


		/// <summary>
		/// (= { symbol value}*)
		/// Setq (Set Quote) is the variable assignment operator. 
		/// Sets each variable symbol to value in the current environment. 
		/// The abbreviation = is more commonly used in Yarr.
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("=;setq;setf")]
        public static Object Setq(Cons args, Environment environment) 
		{
            Object v = null;
            
            while (args != null) 
			{
                Object target = args.First();
                Object source = args.Second();
                args = (Cons)args.Cddr();

                // "setq" on a symbol
                if (target is Symbol)
                {
                    Symbol s = (Symbol)target;

                    if (!s.Name.Contains("."))
                    {
                        environment[(Symbol)target] = v = Runtime.Eval(source, environment);
                        continue;
                    }

                    target = Primitives.ParseDotNetReference(s, null, true, environment);
                }

                // "setf" via a SETF method, or a .NET call
                if (target is Cons)
                {
                    Cons f = (Cons)target;

                    // See if there's a SETF method for this
                    Closure fn = environment.GetSetf((Symbol)f.First());
                    if (fn != null)
                    {
                        Cons setArgs = new Cons(v = Runtime.Eval(source, environment),
                                                    Runtime.EvalList(f.Rest(), environment));

                        Runtime.Apply(fn, setArgs, environment);
                    }
                    else    // Implied .NET reference
                    {
                        v = Runtime.SetDotNetValue(Runtime.Eval(f.First(), environment).ToString(),
                                                   f.Second(), (Cons)f.Cddr(), source,
                                                   environment, true);
                    }
                }
			}

			return v;
		}

        /// <summary>
        /// (decf symbol &optional (increment 1))
        /// Increment the value bound to a symbol
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("Decf")]
        public static Object Decf(Cons args, Environment environment)
        {
            if (args.Length > 2)
                throw new YarrException("Decf: Expecting at most two arguments");

            Object val = Runtime.Eval(args.First(), environment);
            if (val == null)
                throw new YarrException("Decf: Expecting non-null argument");

            Object decr = (args.Length == 1) ? (int)1 : Runtime.Eval(args.Second(), environment);

            TypeCode aType = Type.GetTypeCode(Primitives.TypeOf(val));
            TypeCode bType = decr == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(decr));

            if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                decr = Functions.SubtractHelper(val, decr, aType, bType);
            else
            {
                Type[] t = new Type[2];
                t[0] = Primitives.TypeOf(val);
                t[1] = Primitives.TypeOf(decr);

                MethodInfo mi = MethodCache.FindMethod("op_subtraction", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                if (mi == null)
                    throw new YarrException(
                        String.Format(CultureInfo.CurrentCulture, 
                                "Decf: No subtraction operator defined for ({0},{1})", t[0], t[1]));

                Object[] arg = new Object[2];
                arg[0] = val;
                arg[1] = decr;
                decr = mi.Invoke(t[0], arg);
            }

            Cons setqArgs = new Cons(args.First(), new Cons(decr));

            SpecialForm setq = (SpecialForm)environment.GetValue(Symbol.SETQ);
            return setq(setqArgs, environment);
        }

        /// <summary>
        /// (-- symbol)
        /// Decrements the value bound to a symbol
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("--")]
        public static Object Decr(Cons args, Environment environment)
        {
            if (args.Length != 1)
                throw new YarrException("Decr: Expecting exactly one argument");

            Object val = Runtime.Eval(args.First(), environment);
            if (val == null)
                throw new YarrException("Decr: Expecting non-null argument");

            Object result;
            TypeCode tc = Type.GetTypeCode(Primitives.TypeOf(val));
            if (Primitives.IsNumber(tc))
                result = Functions.SubtractHelper(val, (Int32)1, tc, TypeCode.Int32);
            else
            {
                Type[] t = new Type[1];
                t[0] = Primitives.TypeOf(val);

                MethodInfo mi = MethodCache.FindMethod("op_decrement", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                if (mi == null)
                    throw new YarrException(
                        String.Format(CultureInfo.CurrentCulture, 
                                "Decr: No decrement operator defined for type {0}", t[0]));

                Object[] arg = new Object[1];
                arg[0] = val;
                result = mi.Invoke(t[0], arg);
            }

            Cons setqArgs = new Cons(args.First(), new Cons(result));
            SpecialForm setq = (SpecialForm)environment.GetValue(Symbol.SETQ);
            setq(setqArgs, environment);
            return val;
        }

        /// <summary>
        /// (incf symbol &optional (increment 1))
        /// Increment the value bound to a symbol
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("Incf")]
        public static Object Incf(Cons args, Environment environment)
        {
            if (args.Length > 2)
                throw new YarrException("Incf: Expecting at most two arguments");

            Object val = Runtime.Eval(args.First(), environment);
            if (val == null)
                throw new YarrException("Incf: Expecting non-null argument");

            Object incr = (args.Length == 1) ? (int)1 : Runtime.Eval(args.Second(), environment);

            TypeCode aType = Type.GetTypeCode(Primitives.TypeOf(val));
            TypeCode bType = incr == null ? TypeCode.Empty : Type.GetTypeCode(Primitives.TypeOf(incr));

            if (Primitives.IsNumber(aType) && Primitives.IsNumber(bType))
                incr = Functions.AddHelper(val, incr, aType, bType);
            else
            {
                Type[] t = new Type[2];
                t[0] = Primitives.TypeOf(val);
                t[1] = Primitives.TypeOf(incr);

                MethodInfo mi = MethodCache.FindMethod("op_addition", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                if (mi == null)
                    throw new YarrException(
                        String.Format(CultureInfo.CurrentCulture, 
                            "Incf: No addition operator defined for ({0},{1})", t[0], t[1]));

                Object[] arg = new Object[2];
                arg[0] = val;
                arg[1] = incr;
                incr = mi.Invoke(t[0], arg);
            }

            Cons setqArgs = new Cons(args.First(), new Cons(incr));
            SpecialForm setq = (SpecialForm)environment.GetValue(Symbol.SETQ);
            return setq(setqArgs, environment);
        }

        /// <summary>
        /// (++ symbol)
        /// Increments the value bound to a symbol
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("++")]
        public static Object Incr(Cons args, Environment environment)
        {
            if (args.Length != 1)
                throw new YarrException("Incr: Expecting exactly one argument");

            Object val = Runtime.Eval(args.First(), environment);
            if (val == null)
                throw new YarrException("Incr: Expecting non-null argument");

            Object result;
            TypeCode tc = Type.GetTypeCode(Primitives.TypeOf(val));
            if (Primitives.IsNumber(tc))
                result = Functions.AddHelper(val, (Int32)1, tc, TypeCode.Int32);
            else
            {
                Type[] t = new Type[1];
                t[0] = Primitives.TypeOf(val);

                MethodInfo mi = MethodCache.FindMethod("op_increment", t[0], t, null,
                                                BindingFlags.IgnoreCase |
                                                BindingFlags.Public |
                                                BindingFlags.Static |
                                                BindingFlags.FlattenHierarchy);
                if (mi == null)
                    throw new YarrException(
                        String.Format(CultureInfo.CurrentCulture, 
                                "Incr: No increment operator defined for type {0}", t[0]));

                Object[] arg = new Object[1];
                arg[0] = val;
                result = mi.Invoke(t[0], arg);
            }

            Cons setqArgs = new Cons(args.First(), new Cons(result));
            SpecialForm setq = (SpecialForm)environment.GetValue(Symbol.SETQ);
            setq(setqArgs, environment);
            return val;
        }

        
        /// <summary>
        /// (spawn expression [apartment-state]) 
        /// Like eval except that the expression
        /// is evaluated on a new thread. Returns immediately with
        /// the new thread object, but execution of expressions
        /// continues synchronously. Experimental.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("spawn")]
        public static Object Spawn(Cons args, Environment environment)
        {
            if (args == null || args.Count == 0)
                return null;

            if (args.Count == 1)
                return ThreadAdapter.Fork(args.First(), environment, System.Threading.ApartmentState.MTA);
            else if (args.Count == 2)
                return ThreadAdapter.Fork(args.First(), environment, 
                    (System.Threading.ApartmentState) Runtime.Eval(args.Second(), environment));
            else
                throw new YarrException("Spawn: Expected at most two parameters");
        }


        /// <summary>
        /// (step expression)
        /// If a debugger is attached to the runtime, starts the debugger and single-steps into the expression
        /// Otherwise, runs the expression normally
        /// </summary>
        /// <param name="args">A list containing the expression to evaluate</param>
        /// <param name="environment">The local environment</param>
        /// <returns></returns>
        [SpecialForm("step")]
        public static Object Step (Cons args, Environment environment)
        {
            bool debuggerRunning = Runtime.Profiler is IDebugger;
            Object retValue = null;

            try
            {
                if (!debuggerRunning)
                    Runtime.EnableDebugger();
                else
                    Runtime.SetDebuggerMode(DebuggerMode.SingleStep);
                retValue = Runtime.Eval(args.First(), environment);
            }
            finally
            {
                if (!debuggerRunning)
                    Runtime.DisableDebugger();
                else
                    Runtime.SetDebuggerMode(DebuggerMode.RunToBreakpoint);
            }

            return retValue;
        }

        /// <summary>
        /// (symbol-name symbol) 
        /// Gets the print name of a symbol
        /// </summary>
        /// <param name="args">A list containing a symbol</param>
        /// <param name="environment">The local environment</param>
        /// <returns>Returns the name of a symbol as a string</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "environment")]
        [SpecialForm("symbol-name")]
        public static Object SymbolName (Cons args, Environment environment)
        {
            if (args == null  ||  !(args.First() is Symbol))
                throw new YarrException("symbol-name: argument must be a symbol");

            return ((Symbol)args.First()).Name;
        }


        /// <summary>
        /// (symbol-value symbol) 
        /// Gets the print name of a symbol
        /// </summary>
        /// <param name="args">A list containing a symbol</param>
        /// <param name="environment">The local environment</param>
        /// <returns>Returns the name of a symbol as a string</returns>
        [SpecialForm("symbol-value")]
        public static Object SymbolValue (Cons args, Environment environment)
        {
            if (args == null  ||  !(args.First() is Symbol))
                throw new YarrException("symbol-name: argument must be a symbol");

            return Runtime.Eval(args.First(), environment);
        }


        /// <summary>
		/// (to variable limit expression)
		/// Starting at 0, assigns variable to succesive integers upto and 
		/// including limit. Executes expression on each iteration. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("to")]
        public static Object To(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            if (args.Length < 2)
                throw new YarrException("To: expecting at least two arguments");

            if (!(args.First() is Symbol))
                throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                            "To: {0} is not a symbol", args.First().ToString()));

            Symbol local = (Symbol)args.First();

            int endStop;
            if (!int.TryParse(Runtime.Eval(args.Second(), environment).ToString(), out endStop))
                throw new YarrException(String.Format(CultureInfo.CurrentCulture, 
                            "To: {0} is not an integer", args.Second().ToString()));

            Cons body = (Cons)args.Cddr();
            Object retVal = null;

            if (body != null)
            {
                Environment localEnvironment = Environment.MakeEnvironment(environment);
                localEnvironment.Declare(local, typeof(Int32), 0);

                while ((int)localEnvironment.GetValue(local) < endStop)
                {
                    retVal = Runtime.Do(body, localEnvironment);
                    if (Runtime.Returning)
                        break;

                    localEnvironment.AssignLocal(local, (int)localEnvironment.GetValue(local) + 1);
                }
            }

			return retVal;
		}


		/// <summary>
		/// (trace filename expression*) 
		/// Traces an evaluation of expression* (as if in an implicit do), 
		/// documenting all call and return steps; writes the output as an 
		/// XML file in filename. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("trace")]
        public static Object Trace(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

			string filename = (String)Runtime.Eval(args.First(),environment);
            IProfiler oldProfiler = Runtime.Profiler;

			try 
			{
				Runtime.Profiler = new XmlTracer(filename);
                return Runtime.Do((Cons)args.Rest(), environment);
			}
			finally
			{
				Runtime.Profiler = oldProfiler;
			}		
		}


		/// <summary>
		/// (try expression catch [finally])
		/// The try special form corresponds to the try-catch-finally construct found 
		/// in C#. If   catch is null then there is deemed to be no catch block 
		/// at all. If an exception occurs, the variable it is bound to the Exception 
		/// object in the local environment. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("try")]
        public static Object Try(Cons args, Environment environment) 
		{
            if (args == null)
                return null;

            if (environment == null)
                throw new YarrException("Try: Environment is null");

            Environment localEnv = Environment.MakeEnvironment(environment);

            try 
			{
				return Runtime.Eval(args.First(), localEnv);
			} 
			catch (Exception e) 
			{
				localEnv[Symbol.LAST_EXCPT] = e;
						
				// If a catch form is specified then evaluate it
				if (args.Second() == Symbol.NULL)
					throw;

				return Runtime.Eval(args.Second(), localEnv);
			}
			finally 
			{
				// If a finally form was specified then evaluate it
				if  (args.Count > 2)
					Runtime.Eval(args.Third(), localEnv);
			}
		}


        /// <summary>
        /// (unbind symbol*)
        /// Unbinds symbols from the local environment
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("unbind")]
        public static Object Unbind (Cons args, Environment environment)
        {
            if (environment == null)
                throw new YarrException("Unbind: environment is null");

            object result = null;

            if (args != null)
            {
                foreach (object sym in args)
                {
                    if (sym is String)
                        result = environment.Unbind((String)sym);
                    else if (!(sym is Symbol))
                        throw new YarrException("Unbind: Arguments must be symbols");

                    result = environment.Unbind((Symbol)sym);
                }
            }
            return result;
        }


        /// <summary>
        /// (unbind-setf-method symbol*)
        /// Unbinds previously-bound setf methods
        /// </summary>
        /// <param name="args"></param>
        /// <param name="environment"></param>
        /// <returns></returns>
        [SpecialForm("unbind-setf-method")]
        public static Object UnbindSetf (Cons args, Environment environment)
        {
            if (environment == null)
                throw new YarrException("Unbind-setf-method: environment is null");

            object result = null;
            if (args != null)
            {
                foreach (object sym in args)
                {
                    if (!(sym is Symbol) && !(sym is String))
                        throw new YarrException("Unbind-setf-method: Arguments must be symbols");

                    Symbol method = (sym is Symbol) ? (Symbol)sym : Symbol.FromName((String)sym);
                    result = sym;
                    environment.BindSetf(method, (Closure)null);
                }
            }
            return result;
        }


		/// <summary>
		/// (while test expression*) 
		/// The while special form corresponds to the while construct found 
		/// in most algebraic programming languages. First test is evauated, 
		/// if true then expression* is evaluated. The process continues until 
		/// the evaluation of test is false. 
		/// </summary>
		/// <param name="args"></param>
		/// <param name="environment"></param>
		/// <returns></returns>
		[SpecialForm("while")]
        public static Object While(Cons args, Environment environment) 
		{
			object retVal = null;
            if (args != null  &&  args.Count > 0)
            {
                Object test = args.First();
                Cons body = (Cons)args.Rest();

                while ((Conversions.ObjectToBoolean(Runtime.Eval(test, environment))))
                {
                    retVal = Runtime.Do(body, environment);
                    if (Runtime.Returning)
                        break;
                }
            }

            return retVal;
		}
	}
}
